#include <stdexcept>
#include <string>
#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>
#include <sstream>
#include <fstream>
#include <deque>
#include <tr1/unordered_set>

// boost
#include <boost/algorithm/string/replace.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/regex.hpp>
	
// my libraries
//# define DEBUG_LEVEL 9
#include <get_filename.h>
#include <cdna_prot.h>
#include <debug_func.h>
#include <perlutil.h>
#include <ultoa.h>
#include <bl2seq_mine.h>
#include <portable_timer.h>
#include <progress_indicator.h>
#include <bcpp_parsefasta.h>
#include <bcpp_read_seq.h>
#include <stlhelper.h>
#include <string_hash.h>
#include <print_error.h>

#include "get_arg.h"

using boost::replace_first;
using std::cerr;
using std::cin;
using std::cout;
using std::string;
using std::vector;
using std::ostream;
using std::ofstream;
using std::deque;

using std::istream;
using std::ifstream;
using std::sort;
using std::ostringstream;
using boost::lexical_cast;
using boost::tie;

using cdna_prot::complement_cdna_index;
using cdna_prot::validate_cdna_to_index;
using cdna_prot::cdna_index_to_prot;

//	void complement_cdna_index(std::string& cdna);
//	void validate_cdna_to_index(std::string& sequence);
//	std::string cdna_index_to_prot(const std::string& cdna);


//outstream.write(">%s:frame=%d:strand=%d\n%s\n" % (


//________________________________________________________________________________________

//	t_collect_accessions_from_strm

//________________________________________________________________________________________
using BioCPP::t_collect_sequences_report_errors;
struct t_sixframe_translate
{
	unsigned							cnt_sequences;
	t_progress_indicator&				dots;
	t_collect_sequences_report_errors& 	error_report;
	ostream& 							output_strm;
	unsigned							acc_beg_line;
	t_sixframe_translate(  
										t_progress_indicator&			dots_,
										t_collect_sequences_report_errors& error_report_,
										ostream& output_strm_)
			:
			cnt_sequences(0),
			dots(dots_),
			error_report(error_report_),
			output_strm(output_strm_),
			acc_beg_line(0)
		 {	}



	// callback
	bool operator()(std::string& accession, std::string& sequence,
						unsigned acc_filepos,
						unsigned seq_filepos);
};



bool t_sixframe_translate::operator()(std::string& accession, std::string& sequence,
									unsigned /*acc_filepos*/, unsigned /*seq_filepos*/)
{
	++cnt_sequences;
	
	// get line counts for error messages
	unsigned curr_acc_beg_line = acc_beg_line;
	acc_beg_line += std::count(sequence.begin(), sequence.end(), '\n') + 1;

	// bad sequence!!!
	if (!sequence.length())
	{
		error_report.zero_length_sequence(curr_acc_beg_line);
		return true;
	}
	if (!accession.length())
	{
		error_report.zero_length_accession(curr_acc_beg_line);
		return true;
	}

	// convert to index for speed
	validate_cdna_to_index(sequence);
	for (unsigned rev_strand = 0; rev_strand < 2; ++rev_strand)
	{
		for (unsigned frame = 0; frame < 3; ++frame)
		{
			// print progress dots
			dots+= sequence.length();
			output_strm << ">" << accession << ":frame=" << frame 
						<< ":strand=" << (1-rev_strand) << "\n";
			output_strm << cdna_index_to_prot(sequence.begin() + frame, sequence.end()) << "\n";
		}
		// reverse complement before opposite strand
		complement_cdna_index(sequence);
		std::reverse(sequence.begin(), sequence.end());
	}
	return true;
}




int main(int argc, char* argv[])
{
	debug_func_cerr( 8 );

	t_program_args args;

	try
	{

		// don't use C streams
		std::ios::sync_with_stdio(false);
	
		// get command line arguments
		if (!process_command_line_options(argc, argv, args))
			return 1;
	
		

		// 
		// prepare streams
		// 		
		std::string input_file_name = "STDIN";
		std::ifstream ifs;
		bool use_stdin = !args.file_input.length() || args.file_input == "STDIN" || args.file_input == "-";
		if (!use_stdin)
		{
			ifs.open(args.file_input.c_str());
			if (!ifs)
				throw std::runtime_error("Could not open " + args.file_input);
			input_file_name = get_filename(args.file_input);
		}
		std::istream& input_strm = ifs.is_open() ? ifs : cin;

		std::string output_file_name = "STDOUT";
		std::ofstream ofs;
		bool use_stdout = !args.file_output.length() || args.file_output == "STDOUT" || args.file_output == "-";
		if (!use_stdout)
		{
			ofs.open(args.file_output.c_str());
			if (!ofs)
				throw std::runtime_error("Could not open " + args.file_output);
			output_file_name = get_filename(args.file_output);
		}
		std::ostream& output_strm = ofs.is_open() ? ofs : cout;
			
	

		//
		// translate sequences
		//
		BioCPP::t_collect_sequences_collect_errors collect_errors;
		args.VERBOSE() 	<< "\tTranslating sequences from " 
						<< input_file_name << " to " 
						<< output_file_name << "...\n";
		t_progress_indicator dots(args.VERBOSE(), 50000000);
	
		t_sixframe_translate sixframe_translate(dots, collect_errors, output_strm);
		BioCPP::Process_FA_Sequences_tellg(input_strm, sixframe_translate);
		dots.finish();

		args.VERBOSE() << "\t" << sixframe_translate.cnt_sequences << " sequences.\n";
		args.VERBOSE() << "\tDone...\n";
			

		
	}
	catch (std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return 1;
	}
	catch(...)
	{
		return 1;
	}

	return 0;
}

